package org.tulkas.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Helper class for XML DOM operations. All the methods don't support namespaces.
 * 
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class XmlDomUtil {
	/**
	 * Create a new Document.
	 * 
	 * @return
	 * @throws ParserConfigurationException
	 */
	public static Document newDocument() throws ParserConfigurationException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(false);
		dbf.setValidating(false);
		DocumentBuilder db = dbf.newDocumentBuilder();
		return db.newDocument();
	}

	/**
	 * Create a new Document with a specific root Element name.
	 * 
	 * @param rootName
	 * @return
	 * @throws ParserConfigurationException
	 */
	public static Document newDocument(String rootName)
			throws ParserConfigurationException {
		Document doc = newDocument();
		doc.appendChild(doc.createElement(rootName));
		return doc;
	}

	/**
	 * Convert the XML content of a Document into a string.
	 * 
	 * @param doc
	 * @return
	 * @throws TransformerException
	 * @throws IOException
	 */
	public static String saveToString(Document doc)
			throws TransformerException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		saveToStream(doc, baos);
		byte[] bytes = baos.toByteArray();
		baos.close();
		String encoding = doc.getXmlEncoding();
		String ret = null;
		if (encoding != null)
			ret = new String(bytes, doc.getXmlEncoding());
		else
			ret = new String(bytes);
		return ret;
	}

	/**
	 * Load the XML content from a file and construct a Document.
	 * 
	 * @param fileName
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document loadFromFile(String fileName)
			throws ParserConfigurationException, SAXException, IOException {
		FileInputStream fis = new FileInputStream(fileName);
		Document ret = loadFromStream(fis);
		fis.close();
		return ret;
	}

	/**
	 * Load the XML content from a string and construct a Document.
	 * 
	 * @param content
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document loadFromString(String content)
			throws ParserConfigurationException, SAXException, IOException {
		return loadFromString(content, null);
	}

	/**
	 * Load the XML content from a string and construct a Document, with a specific encoding.
	 * 
	 * @param content
	 * @param encoding
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document loadFromString(String content, String encoding)
			throws ParserConfigurationException, SAXException, IOException {
		byte[] bytes = null;
		if (encoding != null)
			bytes = content.getBytes(encoding);
		else
			bytes = content.getBytes();
		ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
		Document doc = loadFromStream(bais);
		bais.close();
		return doc;
	}

	/**
	 * Load the XML content from a stream and construct a Document
	 * 
	 * @param is
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document loadFromStream(InputStream is)
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(false);
		dbf.setValidating(false);
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document ret = db.parse(is);
		return ret;
	}

	/**
	 * Save the XML content of a Document into a file.
	 * 
	 * @param document
	 * @param fileName
	 * @throws TransformerException
	 * @throws IOException
	 */
	public static void saveToFile(Document document, String fileName)
			throws TransformerException, IOException {
		saveToFile(document, fileName, false);
	}

	/**
	 * Save the XML content of a Document into a file. Can use 'indent' format.
	 * 
	 * @param document
	 * @param fileName
	 * @param indent
	 * @throws TransformerException
	 * @throws IOException
	 */
	public static void saveToFile(Document document, String fileName,
			boolean indent) throws TransformerException, IOException {
		FileOutputStream fileOut = new FileOutputStream(fileName);
		saveToStream(document, fileOut, indent);
		fileOut.close();
	}

	/**
	 * Save the XML content of a Document into a stream
	 * 
	 * @param document
	 * @param os
	 * @throws TransformerException
	 * @throws IOException
	 */
	public static void saveToStream(Document document, OutputStream os)
			throws TransformerException, IOException {
		saveToStream(document, os, false);
	}

	/**
	 * Save the XML content of a Document into a stream. Can use 'indent' format
	 * 
	 * @param document
	 * @param os
	 * @param indent
	 * @throws TransformerException
	 * @throws IOException
	 */
	public static void saveToStream(Document document, OutputStream os,
			boolean indent) throws TransformerException, IOException {
		Source source = new DOMSource(document);
		Result result = new StreamResult(os);
		Transformer xformer = TransformerFactory.newInstance().newTransformer();

		if (indent)
			xformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT,
					"yes");
		xformer.transform(source, result);
	}

	/**
	 * Easy method to get the first sub element from a parent element.
	 * 
	 * @param parent
	 * @return
	 */
	public static Element getFirstSubElement(Element parent) {
		NodeList nodes = parent.getChildNodes();

		for (int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			if (Node.ELEMENT_NODE == node.getNodeType()) {
				return (Element) node;
			}
		}

		return null;
	}

	/**
	 * Easy method to get the first sub element with the specific tag name from a parent element.
	 * 
	 * @param parent
	 * @param tagName
	 * @return
	 */
	public static Element getFirstSubElement(Element parent, String tagName) {
		NodeList nodes = parent.getElementsByTagName(tagName);
		if (nodes.getLength() > 0)
			return (Element) nodes.item(0);
		return null;
	}

	/**
	 * Easy method to get All the sub elements with the specific tag name from a parent element.
	 * 
	 * @param parent
	 * @param tagName
	 * @return
	 */
	public static Element[] getSubElements(Element parent, String tagName) {
		NodeList nodes = parent.getElementsByTagName(tagName);
		Element[] ret = new Element[nodes.getLength()];
		for (int i = 0; i < nodes.getLength(); i++) {
			ret[i] = (Element) nodes.item(i);
		}

		return ret;
	}

	/**
	 * Easy method to get All the sub elements from a parent element.
	 * 
	 * @param parent
	 * @return
	 */
	public static Element[] getSubElements(Element parent) {
		List<Element> list = new ArrayList<Element>();
		NodeList nodes = parent.getChildNodes();
		for (int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			if (Node.ELEMENT_NODE == node.getNodeType()) {
				list.add((Element) node);
			}
		}

		return list.toArray(new Element[list.size()]);
	}

	/**
	 * Get the string content from an element.
	 * 
	 * @param element
	 * @return
	 */
	public static String getStringFromElement(Element element) {
		if (element != null) {
			NodeList nodes = element.getChildNodes();
			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);
				if (Node.TEXT_NODE == node.getNodeType())
					return node.getNodeValue();
			}
		}
		return null;
	}

	/**
	 * Get the string content from the first subelement of a parent element.
	 * 
	 * @param parent
	 * @return
	 */
	public static String getStringFromFirstSubElement(Element parent) {
		Element subEle = getFirstSubElement(parent);
		return getStringFromElement(subEle);
	}

	/**
	 * Get the string content from the first subelement with the speicific tag name of a parent element.
	 * 
	 * @param parent
	 * @param tagName
	 * @return
	 */
	public static String getStringFromFirstSubElement(Element parent,
			String tagName) {
		Element subEle = getFirstSubElement(parent, tagName);
		return getStringFromElement(subEle);
	}

	/**
	 * Get all the string content as an array from the sub elements with the speicific tag name of a parent element.
	 * 
	 * @param parent
	 * @param tagName
	 * @return
	 */
	public static String[] getAllStringsFromSubElements(Element parent,
			String tagName) {
		Element[] eles = getSubElements(parent, tagName);
		String[] ret = new String[eles.length];
		for (int i = 0; i < eles.length; i++) {
			ret[i] = getStringFromElement(eles[i]);
		}
		return ret;
	}
}
